Vue 可以作為獨立的腳本文件使用,無需構建步驟!如果你有一個非前後端分離的歷史項目,並且它已經渲染了大部分的 HTML,或者你的前端邏輯並不復雜,不需要構建步驟,Vue 也提供了另一個適用於此類無構建步驟場景的替代版 Petite-Vue,主要為漸進式增強已有的 HTML 作了特別的優化。功能更加精簡,十分輕量。
相對 Vue3 而言,Petite-Vue 有如下特點:
根據官方解釋,Petite-Vue 是專門為非前後端分離的歷史項目提供和 Vue 相近的響應式開發模式。與完整的 Vue 相比最大的特點是,面對數據的變化 Petite-Vue 採取直接操作 DOM 的方式重新渲染。
具體的使用方式請參考 GitHub,在這裡我想展示兩個示例:
v-if
等內置指令的實現createApp
函數v-if="count === 1"
等表達式運算功能若想構建自己的版本只需在控制台執行 npm run build
即可。
<style>
[v-cloak] {
display: none;
}
</style>
<div v-scope="App"></div>
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module'
createApp({
App: {
$template: `
<span v-cloak v-if="status === 'offline'"> OFFLINE </span>
<span v-else> ONLINE </span>
`,
}
status: 'online'
}).mount('[v-scope]')
</script>
上述代碼最終輸出結果為 <div><span> ONLINE </span></div>
,但渲染過程是直接在 DOM Tree 上進行(分為如下4個步驟),當瀏覽器資源緊張時整個渲染過程將會被用戶一覽無餘:
<div>
<span v-cloak v-if="status === 'offline'"> OFFLINE </span>
<span v-else> ONLINE </span>
</div>
<div>
<span v-if="status === 'offline'"> OFFLINE </span>
<span v-else> ONLINE </span>
</div>
<div>
<span v-if="status === 'offline'"> OFFLINE </span>
</div>
<div>
</div>
<div>
<span> ONLINE </span>
</div>
那麼用戶很有可能會看到閃屏現象:ONLINE ➡️ OFFLINE ONLINE ➡️ OFFLINE ➡️ EMPTY ➡️ ONLINE
<div v-scope="App"></div>
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module'
const App = {
$template: `
<div v-scope="Counter(1)"></div>
<div v-scope="Message()"></div>
`
}
const Counter = initialCount => ({
$template: `
<span></span>
<button @click="handleAdd">ADD</button>
`,
count: initialCount || 0
handleAdd() {
this.count += 1
}
})
const Message = () => {
$template: `<div></div>`
}
createApp({
App,
Counter,
Message,
}).mount('[v-scope]')
</script>
Petite-Vue 雖然沒有提供明確的組件構建方式,但通過 v-scope 屬性我們依然可以採取組件化構建我們的頁面。
但上述例子有明顯的問題採取全局組件註冊機制,如例子中即使 Message 組件不需要還是能引用 Counter 組件,假如註冊的不是 Counter 組件的構造函數,那麼 Counter 的狀態將會被意外修改。
createApp({
Counter: Counter()
})
Petite-Vue 無需構建流程即可使用。只需從 CDN 加載它:
<script src="https://unpkg.com/petite-vue" defer init></script>
<!-- 頁面任意位置 -->
<div v-scope="{ count: 0 }">
{{ count }}
<button @click="count++">inc</button>
</div>
- 這裡的 init 其實也就是
PetiteVue.createApp().mount()
的簡潔寫法;- 閱讀源碼可知:
- const s = document.currentScript;
- 如果 ( s && s . hasAttribute ( 'init ' )) {
- createApp().mount();
- }
- 如果不想自動初始化,那麼移除 init 屬性,且在 script 標籤中,增加
PetiteVue.createApp().mount()
。
如果您不想要自動初始化,請刪除該 init 屬性並將腳本移動到 <body>
末尾:
<script src="https://unpkg.com/petite-vue"></script>
<script>
PetiteVue.createApp().mount()
</script>
或者,使用 ES 模塊構建:
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module'
createApp().mount()
</script>
重點:在開發時期,CDN 地址可以是
https://unpkg.com/petite-vue
這樣簡短的,但對於生產使用時,應該要使用完整解析的 CDN URL。
如:https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.iife.js
或者https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js
(ES 模塊引用時),避免解析和重定向文本之外,還有避免版本不同導致項目出現意外情況。
使用終端命令可以快速簡單下載代碼到本地:
curl unpkg.com/petite-vue@0.4.1/dist/petite-vue.iife.js --output petite-vue@0.4.1-iife.js
這裡的作用域和我們編寫 JavaScript 時說的作用域是一致的,作用是限定函數和變量的可用範圍,減少命名衝突。具有如下特點:
// 全局作用域
var globalVariable = 'hello'
var message1 = 'there'
var message2 = 'bye'
(() => {
// 局部作用域 A
let message1 = '局部作用域A'
message2 = 'see you'
console.log(globalVariable, message1, message2)
})()
// 輸出:hello 局部作用域 A see you
(() => {
// 局部作用域 B
console.log(globalVariable, message1, message2)
})()
// 輸出:hello there see you
createApp()
,它接收一個數據對像作為根範圍中的變量,供模版中使用。
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module';
createApp({
count: 0,
// getters
get plusOne() {
return this.count + 1;
},
increment() {
this.count++;
},
}).mount();
</script>
<!-- v-scope value can be omitted -->
<div v-scope>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
<button @click="increment">increment</button>
</div>
這樣在模版中就可以使用 count
變量、increment
方法了。
mount()
沒有傳入掛載元素時,Petite-Vue 作用於整個頁面,但當傳入掛載元素時,那麼僅作用於掛載元素及其內的元素。
這也意味著,我們可以在同一頁面掛載多個 Petite-Vue 應用,每一個應用都有其獨立的根變量範圍。
createApp({
// root scope for app one
}).mount('#app1');
createApp({
// root scope for app two
}).mount('#app2');
在 Petite-Vue 中,可以監聽每一個元素的掛載和卸載事件。
在 v0.4.0 開始,綁定生命週期事件需要加上 @vue:
前綴。
<!-- v0.4.0 以下 -->
<div v-if="show" @mounted="console.log('mounted on: ', $el)" @unmounted="console.log('unmounted: ', $el)">
some node
</div>
<!-- v0.4.0 以上 -->
<div v-if="show" @vue:mounted="console.log('mounted on: ', $el)" @vue:unmounted="console.log('unmounted: ', $el)">
some node
</div>
用 v-effect
執行響應式內聯語句:
<div v-scope="{ count: 0 }">
<div v-effect="$el.textContent = count"></div>
<button @click="count++">++</button>
</div>
effect 響應式的追蹤其依賴,並在依賴更改時重新執行,因此每當更改 count 時它都會重新運行。
組件的概念在 Petite-Vue 中有所不同,因為它更加簡單。
組件有兩種方式來創建,分別是純數據的函數組件
和帶有模板的函數組件
。
使用組件,是需要在元素中用 v-scope
來調用函數。
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module'
function Counter(props) {
return {
count: props.initialCount,
inc() {
this.count++
},
mounted() {
console.log(`I'm mounted!`)
}
}
}
createApp({
Counter
}).mount()
</script>
<div v-scope="Counter({ initialCount: 1 })" @vue:mounted="mounted">
<p>{{ count }}</p>
<button @click="inc">increment</button>
</div>
<div v-scope="Counter({ initialCount: 2 })">
<p>{{ count }}</p>
<button @click="inc">increment</button>
</div>
如果還需要模板的話,相比函數組件是多了一個字段來聲明模板:$template
,該字段的值可以是一個模板字符串,也可以是 <template>
元素的 ID 選擇器。(推薦用 template
元素)。
<script type="module">
import { createApp } from 'https://unpkg.com/petite-vue?module';
function Counter(props) {
return {
$template: '#counter-template',
// $template: `
// My count is {{count}}
// <button @click="inc">+aaa+</button>
// `,
count: props.initialCount,
inc() {
this.count++;
},
};
}
createApp({
Counter,
}).mount();
</script>
<template id="counter-template">
My count is {{ count }}
<button @click="inc">++</button>
</template>
<!-- reuse it -->
<div v-scope="Counter({ initialCount: 1 })"></div>
<div v-scope="Counter({ initialCount: 2 })"></div>
沒錯,即便是簡單的 mini Vue,也可以有全局狀態管理。這裡使用的是 vue3 的 reactive
API,來實現全局狀態管理。
<script type="module">
import { createApp, reactive } from 'https://unpkg.com/petite-vue?module'
const store = reactive({
count: 0,
inc() {
this.count++
}
})
// manipulate it here
store.inc()
createApp({
// share it with app scopes
store
}).mount()
</script>
<div v-scope="{ localCount: 0 }">
<p>Global {{ store.count }}</p>
<button @click="store.inc">increment</button>
<p>Local {{ localCount }}</p>
<button @click="localCount++">increment</button>
</div>
@
):
)Petite-Vue 的自定義指令與 vue 有些不同,那麼怎麼註冊一個指令?
const myDirective = (ctx) => {};
createApp().directive('dir-name', dirFn).mount()
v-html
指令的實現:
const html = ({ el, get, effect }) => {
// effect 每次 get() 更改後就會執行
effect(() => {
el.innerHTML = get();
});
};
自定義指令範例:
const myDirective = (ctx) => {
// the element the directive is on
ctx.el
// the raw value expression
// e.g. v-my-dir="x" then this would be "x"
ctx.exp
// v-my-dir:foo -> "foo"
ctx.arg
// v-my-dir.mod -> { mod: true }
ctx.modifiers
// evaluate the expression and get its value
ctx.get()
// evaluate arbitrary expression in current scope
ctx.get(`${ctx.exp} + 10`)
// run reactive effect
ctx.effect(() => {
// this will re-run every time the get() value changes
console.log(ctx.get())
})
return () => {
// cleanup if the element is unmounted
}
}
// register the directive
createApp().directive('my-dir', myDirective).mount()
這個可以通過給 createApp
的配置項增加屬性 $delimiters: ['${', '}']
來實現,通常在服務器端模板語言一起使用時比較有用。
Petite-Vue 僅有的特性:
不同的功能:
$el
指向指令綁定到的當前元素(而不是組件根元素)createApp()
接受全局狀態而不是組件共用的特性:
共有的特性 |
---|
{{ }} |
v-bind |
v-on |
v-model |
v-if / v-else / v-else-if |
v-for |
v-show |
v-html |
v-text |
v-pre |
v-once |
v-cloak |
reactive() |
nextTick() |
Template refs |
Petite-Vue 不支持的特性:
一些特性因為它們在漸進增強的背景下具有相對較低的使用頻率而被丟棄。
如果你需要這些特性,你可能應該只使用標準的 Vue:
ref()
、computed()
等組合式APIRender functions
( Petite-Vue 沒有 Virtual DOM)Reactivity
響應式數據類型(Map、Set、...等,為了體積更小而刪除)Transition
、KeepAlive
、Teleport
、Suspense
v-for
的深解構v-on="object"
對象語法v-is
動態組件v-bind:style
自動前綴總結來說,Petite-Vue 這個項目,挺適合「用不到 Vue、React 等這些偏大的前端框架的簡單頁面」,或是「既有的非前後端分離的歷史項目」來做使用的,有著 Vue 的開發體驗,也能不增加項目的複雜度,簡直是一個完美的體驗!
作者:Wayne (偉恩)
連結:https://wayne-blog.com/
來源:Wayne's blog | 偉恩的部落格 | 技術博客